﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;

#if WINDOWS_PHONE
using Microsoft.Phone.Reactive;
#else
using System.Reactive.Linq;
#endif

namespace ReactiveOAuth
{

    /// <summary>
    /// OAuth Authorization Client
    /// </summary>
    public class OAuthAuthorizer : OAuthBase
    {

        public OAuthAuthorizer(string consumerKey, string consumerSecret)
            : base(consumerKey, consumerSecret)
        {
            
        }

        private IObservable<TokenResponse<T>> GetTokenResponse<T>(string url,
                                                                  IEnumerable<Parameter> parameters,
                                                                  Func<string, string, T> tokenFactory) where T : Token
        {
            var req = (HttpWebRequest) WebRequest.Create(url);
            req.Headers[HttpRequestHeader.Authorization] = BuildAuthorizationHeader(parameters);
            req.Method = MethodType.Post.ToUpperString();
            req.ContentType = "application/x-www-form-urlencoded";

            return req.DownloadStringAsync().Select(tokenBase =>
                    {
                        var splitted = tokenBase.Split('&').Select(s => s.Split('=')).ToDictionary(s => s.First(),
                                                                                                    s => s.Last());
                        var token = tokenFactory(splitted["oauth_token"], splitted["oauth_token_secret"]);
                        var extraData = splitted.Where(kvp => kvp.Key != "oauth_token"
                                                            && kvp.Key != "oauth_token_secret")
                            .ToLookup(kvp => kvp.Key, kvp => kvp.Value);
                        return new TokenResponse<T>(token, extraData);
                    });
        }

        private IObservable<String> GetTokenResponse(string url, IEnumerable<Parameter> parameters)
        {
            var req = (HttpWebRequest)WebRequest.Create(url);
            req.Headers[HttpRequestHeader.Authorization] = BuildAuthorizationHeader(parameters);
            req.Method = MethodType.Post.ToUpperString();
            req.ContentType = "application/x-www-form-urlencoded";

            return req.DownloadStringAsync().Select(tokenBase => tokenBase);
        }

        /// <summary>
        /// construct AuthrizeUrl + RequestTokenKey
        /// </summary>
        public string BuildAuthorizeUrl(string authUrl, string consumerKey, RequestToken requestToken)
        {
            Guard.ArgumentNull(authUrl, "authUrl");
            Guard.ArgumentNull(consumerKey, "consumerKey");
            Guard.ArgumentNull(requestToken, "accessToken");

            return String.Format("{0}?key={1}&token={2}", authUrl, consumerKey, requestToken.Key);
        }

        /// <summary>asynchronus get RequestToken</summary>
        public IObservable<TokenResponse<RequestToken>> GetRequestToken(string requestTokenUrl)
        {
            Guard.ArgumentNull(requestTokenUrl, "requestTokenUrl");

            var parameters = ConstructBasicParameters(requestTokenUrl, MethodType.Post);

            return GetTokenResponse(requestTokenUrl, parameters, (key, secret) => new RequestToken(key, secret));
        }

        /// <summary>asynchronus get GetAccessToken</summary>
        public IObservable<TokenResponse<AccessToken>> GetAccessToken(string accessTokenUrl, RequestToken requestToken, string verifier)
        {
            Guard.ArgumentNull(accessTokenUrl, "accessTokenUrl");
            Guard.ArgumentNull(requestToken, "requestToken");
            Guard.ArgumentNull(verifier, "verifier");

            var verifierParam = new Parameter("oauth_verifier", verifier);
            var parameters = ConstructBasicParameters(accessTokenUrl, MethodType.Post, requestToken, verifierParam);

            return GetTokenResponse(accessTokenUrl, parameters, (key, secret) => new AccessToken(key, secret));
        }

        public IObservable<String> RenewAccessToken(string accessTokenUrl, AccessToken accessToken)
        {
            return AccessTokenAction(accessTokenUrl, accessToken);
        }

        public IObservable<String> RevokeAccessToken(string accessTokenUrl, AccessToken accessToken)
        {
            return AccessTokenAction(accessTokenUrl, accessToken);
        }

        private IObservable<String> AccessTokenAction(string accessTokenUrl, AccessToken accessToken)
        {
            Guard.ArgumentNull(accessTokenUrl, "accessTokenUrl");
            Guard.ArgumentNull(accessToken, "accessToken");

            var parameters = ConstructBasicParameters(accessTokenUrl, MethodType.Post, accessToken);

            return GetTokenResponse(accessTokenUrl, parameters);
        }
        
        ///// <summary>asynchronus get GetAccessToken for xAuth</summary>
        //public IObservable<TokenResponse<AccessToken>> GetAccessToken(string accessTokenUrl, Token accessToken, string xauthUserName, string xauthPassword, string xauthMode = "client_auth")
        //{
        //    Guard.ArgumentNull(accessTokenUrl, "accessTokenUrl");
        //    Guard.ArgumentNull(xauthUserName, "xauthUserName");
        //    Guard.ArgumentNull(xauthPassword, "xauthPassword");
        //    Guard.ArgumentNull(xauthMode, "xauthMode");

        //    var xauthParams = new ParameterCollection
        //    {
        //        {"x_auth_username", xauthUserName},
        //        {"x_auth_password", xauthPassword},
        //        {"x_auth_mode", xauthMode}
        //    };
        //    var parameters = ConstructBasicParameters(accessTokenUrl, MethodType.Post, accessToken, xauthParams);

        //    return GetTokenResponse(accessTokenUrl + "?" + xauthParams.ToQueryParameter(),
        //        parameters, (key, secret) => new AccessToken(key, secret));
        //}


    }

}